import wx
import os.path
import wx.stc as stc
import __main__

import stylesDefination

from utils import tracer
from editorStyle import EditorStyle
from utils.eventHandler import EventHandler, EventArgs

class Editor(wx.Panel):
  Languages = {'py':stylesDefination.LANGUAGE_PYTHON, 
              'xml':stylesDefination.LANGUAGE_XML}

  def __init__(self, parent, document):
    wx.Panel.__init__(self, parent)
    self._editorStyle = None
    self._document = document

    self._document.fileNameChangedEventHandler(self.onFilenameChanged)

    sizer = wx.BoxSizer(wx.VERTICAL)
    self.SetSizer(sizer)

    if wx.Platform == '__WXGTK__':
      self._toolbar = self.createToolbar()
      sizer.Add(self._toolbar, False, wx.EXPAND)
    
    self._editView = stc.StyledTextCtrl(self)
    sizer.Add(self._editView, True, wx.EXPAND)
    
    wx.CallAfter(self.Layout)
    
    self._editView.Bind(wx.stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
    self._editView.Bind(wx.stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
    self._editView.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)

    self._savePointLeftEventHandler = EventHandler()
    self.Bind(wx.stc.EVT_STC_SAVEPOINTLEFT, self.onSavePointLeft)

    self._savePointReachedEventHandler = EventHandler()
    self.Bind(wx.stc.EVT_STC_SAVEPOINTREACHED, self.onSavePointReached)

  # -- toolbar -----------------------------------------------------------------------
  def createToolbar(self):
    toolbar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize,
                        wx.TB_FLAT | wx.TB_NODIVIDER)
    toolbar.SetToolBitmapSize(wx.Size(20, 20))
    
    iconSize = wx.Size(16, 16)

    # save 
    bmp = wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, iconSize)
    toolbar_item_save = toolbar.AddLabelTool(10, "Save", bmp, bmp, wx.ITEM_NORMAL, 'Save File')
    toolbar.Bind(wx.EVT_TOOL, self.onSaveFile, id=10)

    # add cut button
    bmp = wx.ArtProvider.GetBitmap(wx.ART_CUT, wx.ART_TOOLBAR, iconSize)
    toolbar_item_cut = toolbar.AddLabelTool(20, "Cut", bmp, bmp, wx.ITEM_NORMAL, 'Cut Selections')
    toolbar.Bind(wx.EVT_TOOL, self.onCut, id=20)

    # add copy button
    bmp = wx.ArtProvider.GetBitmap(wx.ART_COPY, wx.ART_TOOLBAR, iconSize)
    toolbar_item_copy = toolbar.AddLabelTool(30, "Copy", bmp, bmp, wx.ITEM_NORMAL, 'Copy Selections')
    toolbar.Bind(wx.EVT_TOOL, self.onCopy, id=30)

    # add paste button
    bmp = wx.ArtProvider.GetBitmap(wx.ART_PASTE, wx.ART_TOOLBAR, iconSize)
    toolbar_item_paste = toolbar.AddLabelTool(40, "Paste", bmp, bmp, wx.ITEM_NORMAL, 'Paste')
    toolbar.Bind(wx.EVT_TOOL, self.onPaste, id=40)

    # add delete button
    bmp = wx.ArtProvider.GetBitmap(wx.ART_DELETE, wx.ART_TOOLBAR, iconSize)
    toolbar_item_delete = toolbar.AddLabelTool(50, "Delete", bmp, bmp, wx.ITEM_NORMAL, 'Delete Selections')
    toolbar.Bind(wx.EVT_TOOL, self.onDelete, id=50)

    # indent
    bmp = wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR, iconSize)
    toolbar_item_indent = toolbar.AddLabelTool(60, "Indent", bmp, bmp, wx.ITEM_NORMAL, 'Indent Selections')
    toolbar.Bind(wx.EVT_TOOL, self.onIndent, id=60)

    # unindent
    bmp = wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR, iconSize)
    toolbar_item_unindent = toolbar.AddLabelTool(70, "Unindent", bmp, bmp, wx.ITEM_NORMAL, 'Unindent Selections')
    toolbar.Bind(wx.EVT_TOOL, self.onUnindent, id=70)

    # comment
    bmp = wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR, iconSize)
    toolbar_item_unindent = toolbar.AddLabelTool(80, "Comment", bmp, bmp, wx.ITEM_NORMAL, 'Comment Selections')
    toolbar.Bind(wx.EVT_TOOL, self.onComment, id=80)

    # uncomment
    bmp = wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR, iconSize)
    toolbar_item_unindent = toolbar.AddLabelTool(90, "Uncomment", bmp, bmp, wx.ITEM_NORMAL, 'Uncomment Selections')
    toolbar.Bind(wx.EVT_TOOL, self.onUncomment, id=90)

    return toolbar

  def onSaveFile(self, evt):
    self.saveFile()

  def onCut(self, evt):
    self.cut()

  def onCopy(self, evt):
    self.copy()

  def onPaste(self, evt):
    self.paste()

  def onDelete(self, evt):
    self.delete()

  def onIndent(self, evt):
    self.indentSelection()

  def onUnindent(self, evt):
    self.unindentSelection()

  def onComment(self, evt):
    self.comment()

  def onUncomment(self, evt):
    self.uncomment()

  def cut(self):
    self._editView.Cut()

  def copy(self):
    self._editView.Copy()

  def paste(self):
    self._editView.Paste()

  def delete(self):
    self._editView.DeleteBack()

  def indentSelection(self):
    selection = self._editView.GetSelection()
    startLine = self._editView.LineFromPosition(selection[0])
    endLine = self._editView.LineFromPosition(selection[1])

    for line in range(min(startLine, endLine), max(startLine, endLine) + 1):
      self._editView.SetLineIndentation(line, self._editView.GetLineIndentation(line) + self._editView.GetIndent())

  def unindentSelection(self):
    selection = self._editView.GetSelection()
    startLine = self._editView.LineFromPosition(selection[0])
    endLine = self._editView.LineFromPosition(selection[1])

    for line in range(min(startLine, endLine), max(startLine, endLine) + 1):
      self._editView.SetLineIndentation(line, self._editView.GetLineIndentation(line) - self._editView.GetIndent())

  def comment(self):
    commentSymbols = self._editorStyle.getCommentSymbols()
    assert len(commentSymbols) == 2

    selection = self._editView.GetSelection()
    startLine = self._editView.LineFromPosition(selection[0])
    endLine = self._editView.LineFromPosition(selection[1])

    if commentSymbols[1] == '':
      for line in range(min(startLine, endLine), max(startLine, endLine) + 1):
        lineText = self._editView.GetLine(line)
        lineText = '%s%s' % (commentSymbols[0], lineText)
        self.replaceLine(line, lineText)
    else:
      frontLineNo = min(startLine, endLine)
      frontLine = self._editView.GetLine(frontLineNo)
      frontLine = '%s%s' % (commentSymbols[0], frontLine)
      self.replaceLine(frontLineNo, frontLine)

      backLineNo = max(startLine, endLine)
      backLine = self._editView.GetLine(backLineNo)
      backLine = '%s%s' % (backLine.rstrip(' \t%s' % os.linesep), commentSymbols[1])
      self.replaceLine(backLineNo, backLine)

  def uncomment(self):
    commentSymbols = self._editorStyle.getCommentSymbols()
    assert len(commentSymbols) == 2

    selection = self._editView.GetSelection()
    startLine = self._editView.LineFromPosition(selection[0])
    endLine = self._editView.LineFromPosition(selection[1])

    if commentSymbols[1] == '':
      for line in range(min(startLine, endLine), max(startLine, endLine) + 1):
        lineText = self._editView.GetLine(line)
        if lineText.lstrip(' \t').startswith(commentSymbols[0]):
          lineText = lineText.replace(commentSymbols[0], '', 1)
          self.replaceLine(line, lineText)
    else:
      pass

  def replaceLine(self, lineNo, targetString):
    startPos = self._editView.PositionFromLine(lineNo)
    endPos = self._editView.GetLineEndPosition(lineNo)
    self._editView.SetSelection(startPos, endPos)
    self._editView.ReplaceSelection(targetString.rstrip(' \t\r\n')) ##%s' % os.linesep))

  def foldAll(self):
    ##  >>> wx.stc.STC_FOLDLEVELBASE
    ##  0x400 = 1024
    ##  >>> wx.stc.STC_FOLDLEVELBOXFOOTERFLAG
    ##  0x8000 = 32768
    ##  >>> wx.stc.STC_FOLDLEVELBOXHEADERFLAG
    ##  0x4000 = 16384
    ##  >>> wx.stc.STC_FOLDLEVELCONTRACTED
    ##  0x10000 = 65536
    ##  >>> wx.stc.STC_FOLDLEVELHEADERFLAG
    ##  0x2000 = 8192
    ##  >>> wx.stc.STC_FOLDLEVELNUMBERMASK
    ##  0xfff = 4095
    ##  >>> wx.stc.STC_FOLDLEVELUNINDENT
    ##  0x20000 = 131072
    ##  >>> wx.stc.STC_FOLDLEVELWHITEFLAG
    ##  0x1000 = 4096
    foldLevel = []
    lineCount = self._editView.GetLineCount()
    visibleLines = self._editView.LinesOnScreen()
    firstVisibleLine = self._editView.GetFirstVisibleLine()
    originalFirstLine = firstVisibleLine

    for lineNo in range(lineCount):
      if lineNo == firstVisibleLine + visibleLines / 3:
        firstVisibleLine += visibleLines / 3
        self._editView.ScrollToLine(firstVisibleLine)

      expanded = self._editView.GetFoldExpanded(lineNo)
      foldParent = self._editView.GetFoldParent(lineNo)

      level = self._editView.GetFoldLevel(lineNo)
      # 9216 = wx.STC_FOLDLEVELHEADERFLAG | wx.STC_FOLDLEVELBASE
      #        0x2000 | 0x400
      level = (level - 9216) / 2
      if expanded and level >= 0:
        if len(foldLevel) <= level:
          for lvl in range(level-len(foldLevel) + 1):
            foldLevel.append([])
        foldLevel[level].append(lineNo)

    foldLevel.reverse()
    for levelItems in foldLevel:
      for foldLineNo in levelItems:
        self._editView.SetFoldExpanded(foldLineNo, False)
        lastChild = self._editView.GetLastChild(foldLineNo, -1)
        if lastChild > foldLineNo:
          self._editView.HideLines(foldLineNo + 1, lastChild)

    self._editView.ScrollToLine(originalFirstLine)

  def expandAll(self):
    foldLevel = []
    lineCount = self._editView.GetLineCount()
    visibleLines = self._editView.LinesOnScreen()
    firstVisibleLine = self._editView.GetFirstVisibleLine()
    originalFirstLine = firstVisibleLine

    for lineNo in range(lineCount):
      if lineNo == firstVisibleLine + visibleLines / 3:
        firstVisibleLine += visibleLines / 3
        self._editView.ScrollToLine(firstVisibleLine)

      expanded = self._editView.GetFoldExpanded(lineNo)
      foldParent = self._editView.GetFoldParent(lineNo)

      level = self._editView.GetFoldLevel(lineNo)
      # 9216 = wx.STC_FOLDLEVELHEADERFLAG | wx.STC_FOLDLEVELBASE
      #        0x2000 | 0x400
      level = (level - 9216) / 2
      if not expanded and level >= 0:
        if len(foldLevel) <= level:
          for lvl in range(level-len(foldLevel) + 1):
            foldLevel.append([])
        foldLevel[level].append(lineNo)

    foldLevel.reverse()
    for levelItems in foldLevel:
      for foldLineNo in levelItems:
        self._editView.SetFoldExpanded(foldLineNo, True)
        lastChild = self._editView.GetLastChild(foldLineNo, -1)
        if lastChild > foldLineNo:
          self._editView.ShowLines(foldLineNo + 1, lastChild)

    self._editView.ScrollToLine(originalFirstLine)

    def fold(self, lineNo):
      expanded = self._editView.GetFoldExpanded(lineNo)
      level = (self._editView.GetFoldLevel(lineNo) - 9216) / 2
      if expanded:
        if level < 0:
          lineNo = self._editView.GetFoldParent(lineNo)
      
        self._editView.SetFoldExpanded(lineNo, False)
        lastChild = self._editViwe.GetLastChild(lineNo, -1)
        if lastChild > lineNo:
          self._editView.HideLines(lineNo + 1, lastChild)

    def expand(self, lineNo):
      expanded = self._editView.GetFoldExpanded(lineNo)
      level = (self._editView.GetFoldLevel(lineNo) - 9216) / 2
      if not expanded:
        if level < 0:
          lineNo = self._editView.GetFoldParent(lineNo)
      
        self._editView.SetFoldExpanded(lineNo, True)
        lastChild = self._editViwe.GetLastChild(lineNo, -1)
        if lastChild > lineNo:
          self._editView.HideLines(lineNo + 1, lastChild)

  # -- methods ---------------------------------------------------------------------------
  def getModify(self):
    return self._editView.GetModify()

  def setEditorStyle(self, language):
    if not self._editorStyle is None:
      self._editorStyle.setOwner(None)

    editorStyle = EditorStyle(language)
    self._editorStyle = editorStyle
    self._editorStyle.setEditorView(self._editView)
    
  def openFile(self):
    filename = self._document.getFilename()    
    if filename == '':
      return
    
    tracer.writeln('Trying to open file: %s' % filename)

    assert os.path.exists(filename)

    self._editView.LoadFile(filename)
    self._editView.EmptyUndoBuffer()

    tracer.writeln('Trying to set highlight style for special file type.')
    
    language = self.getLanguageByFilename(filename)
    self.setEditorStyle(language)

    tracer.writeln('Open file succeed.')

  def saveFile(self):
    filename = self._document.getFilename()
    if filename == '':
      return False
    return self._editView.SaveFile(filename)

  def setCurLine(self, lineNo):
    lineCnt = self._editView.GetLineCount()
    if lineNo > lineCnt:
      lineNo = lineCnt
    elif lineNo < 0:
      lineNo = 0

    # scroll view to line
    self._editView.ScrollToLine(lineNo)

    # move caret to current line
    currentPos = self._editView.PositionFromLine(lineNo)
    self._editView.SetSelection(currentPos, currentPos)
    if not self._editView.GetSTCFocus():
      self._editView.SetSTCFocus(True)

  def getLanguageByFilename(self, filename):
    basename = os.path.basename(filename)
    tmp = basename.split(os.path.extsep)
    
    language = ''
    if len(tmp) == 2 and Editor.Languages.has_key(tmp[1].lower()):
      language = Editor.Languages[tmp[1].lower()]
    return language

  # -- document events -----------------------------------------------------
  def savePointLeftEventHandler(self, handler):
    self._savePointLeftEventHandler.add(handler)
  def onSavePointLeft(self, evt):
    eventArgs = EventArgs((self._document,))
    self._savePointLeftEventHandler.invoke(self, eventArgs)
  
  def savePointReachedEventHandler(self, handler):
    self._savePointReachedEventHandler.add(handler)
  def onSavePointReached(self, evt):
    eventArgs = EventArgs((self._document,))
    self._savePointReachedEventHandler.invoke(self, eventArgs)

  # -- document event processers -------------------------------------------
  def onFilenameChanged(self, sender, args):
    filename = args.getValue()[1]
    language = self.getLanguageByFilename(filename)
    self.setEditorStyle(language)

  # -- process events ------------------------------------------------------
  def OnKeyPressed(self, event):
    if self._editView.CallTipActive():
      self._editView.CallTipCancel()
    dir(event)

    key = event.GetKeyCode()
    if key == 32 and event.ControlDown():
      pos = self._editView.GetCurrentPos()

      # Tips
      if event.ShiftDown():
        self._editView.CallTipSetBackground("yellow")
        self._editView.CallTipShow(pos, 'lots of of text: blah, blah, blah\n\n' \
                         'show some suff, maybe parameters..\n\n' \
                         'fubar(param1, param2)')
      # Code completion
      else:
        #lst = []
        #for x in range(50000):
        #    lst.append('%05d' % x)
        #st = " ".join(lst)
        #print len(st)
        #self.AutoCompShow(0, st)

        kw = keyword.kwlist[:]
        kw.append("zzzzzz?2")
        kw.append("aaaaa?2")
        kw.append("__init__?3")
        kw.append("zzaaaaa?2")
        kw.append("zzbaaaa?2")
        kw.append("this_is_a_longer_value")
        #kw.append("this_is_a_much_much_much_much_much_much_much_longer_value")

        kw.sort()  # Python sorts are case sensitive
        self._editView.AutoCompSetIgnoreCase(False)  # so this needs to match

        # Images are specified with a appended "?type"
        for i in range(len(kw)):
          if kw[i] in keyword.kwlist:
            kw[i] = kw[i] + "?1"

        self._editView.AutoCompShow(0, " ".join(kw))
    else:
      event.Skip()


  def OnUpdateUI(self, evt):
    # check for matching braces
    braceAtCaret = -1
    braceOpposite = -1
    charBefore = None
    caretPos = self._editView.GetCurrentPos()

    if caretPos > 0:
      charBefore = self._editView.GetCharAt(caretPos - 1)
      styleBefore = self._editView.GetStyleAt(caretPos - 1)

    # check before
    if charBefore and chr(charBefore) in "[]{}()" and styleBefore == wx.stc.STC_P_OPERATOR:
      braceAtCaret = caretPos - 1

    # check after
    if braceAtCaret < 0:
      charAfter = self._editView.GetCharAt(caretPos)
      styleAfter = self._editView.GetStyleAt(caretPos)

      if charAfter and chr(charAfter) in "[]{}()" and styleAfter == wx.stc.STC_P_OPERATOR:
        braceAtCaret = caretPos

    if braceAtCaret >= 0:
      braceOpposite = self._editView.BraceMatch(braceAtCaret)

    if braceAtCaret != -1  and braceOpposite == -1:
      self._editView.BraceBadLight(braceAtCaret)
    else:
      self._editView.BraceHighlight(braceAtCaret, braceOpposite)


  def OnMarginClick(self, evt):
    # fold and unfold as needed
    tracer.writeln('Margin clicked.')
    if evt.GetMargin() == 1:
      tracer.writeln('Process Folding and Expanding.')
      if evt.GetShift() and evt.GetControl():
        self.foldAll()
      else:
        tracer.writeln('Expand clicked.')
        lineClicked = self._editView.LineFromPosition(evt.GetPosition())

        if self._editView.GetFoldLevel(lineClicked) & wx.stc.STC_FOLDLEVELHEADERFLAG:
          if evt.GetShift():
            self._editView.SetFoldExpanded(lineClicked, True)
            self.expand(lineClicked, True, True, 1)
          elif evt.GetControl():
            if self._editView.GetFoldExpanded(lineClicked):
              self._editView.SetFoldExpanded(lineClicked, False)
              self.expand(lineClicked, False, True, 0)
            else:
              self._editView.SetFoldExpanded(lineClicked, True)
              self.expand(lineClicked, True, True, 100)
          else:
            self._editView.ToggleFold(lineClicked)


  
